using System;
using System.Collections.Generic;
using System.Text;
using NHibernate;
using Iesi.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Xsl;
using System.Data;
using System.Data.SqlClient;
using System.Web;

namespace TanashCMS.Business
{
    public enum ImportStatus
    {
        New,
        InProgress,
        Completed,
        Failed
    }

    public struct InventoryImportProcess
    {
        public ImportStatus Status;
        public int InventoryImportID;
        public string LastError;
        public string CSVFileName;
        public int Progress;
    }

    public class InventoryImport
    {
        private int mID;
        private DateTime mDateCreated;
        private DateTime? mDateCompleted;
        private ImportStatus mStatus;
        private string mLastError, mCSVFileName;
        private int mWebsiteID;

        public virtual int ID { get { return mID; } set { mID = value; } }
        public virtual int WebsiteID { get { return mWebsiteID; } set { mWebsiteID = value; } }
        public virtual DateTime DateCreated { get { return mDateCreated; } set { mDateCreated = value; } }
        public virtual DateTime? DateCompleted { get { return mDateCompleted; } set { mDateCompleted = value; } }
        public virtual ImportStatus Status { get { return mStatus; } set { mStatus = value; } }
        public virtual string LastError { get { return mLastError; } set { mLastError = value; } }
        public virtual string CSVFileName { get { return mCSVFileName; } set { mCSVFileName = value; } }

        public static string WriteInventoryToCSVFile(int WebsiteID)
        {
            HttpContext context = HttpContext.Current;
            TanashWebsite website = Configuration.GetCurrentSession().Get<TanashWebsite>(WebsiteID);

            string csvFilename = website.SubFolderName + ".inventory.csv";
            string path = context.Server.MapPath("~/manager/inventory/export").TrimEnd('\\') + @"\";

            //while (File.Exists(path + csvFilename))
            //    csvFilename = Guid.NewGuid().ToString() + ".csv";

            FileStream fstream = new FileStream(path + csvFilename, FileMode.Create);

            StringBuilder sb = new StringBuilder();
            StreamWriter writer = new StreamWriter(fstream);


            List<ProductOptionType> optionTypes = ProductOptionType.List(WebsiteID);

            List<Product> inventory = (List<Product>)Configuration.GetCurrentSession().CreateQuery("select p from Product p where p.WebsiteID = " + WebsiteID.ToString()).List<Product>();

            // first write the headers to the CSV file
            writer.Write("Product Title,UPC,Price,Discount,Category");
            foreach (ProductOptionType optionType in optionTypes)
            {
                writer.Write("," + optionType.Title);

            }
            writer.Write(writer.NewLine);

            foreach (Product product in inventory)
            {
                writer.Write(product.Title + ",");
                writer.Write(product.UPC + ",");
                writer.Write(product.Price + ",");
                writer.Write(product.Discount + ",");
                if (product.CategoryID != 0)
                {
                    Category cat = Configuration.GetCurrentSession().Get<Category>(product.CategoryID);
                    writer.Write(cat.Title);
                }
                List<ProductOption> options = (List<ProductOption>)Configuration.GetCurrentSession().CreateQuery("select o from ProductOption o where o.ProductID = " + product.ID.ToString()).List<ProductOption>();
                foreach (ProductOption option in options)
                {
                    writer.Write("," + option.Value);
                }
                writer.Write(writer.NewLine);
            }

            writer.Close();
            fstream.Close();
            return "~/manager/inventory/export/" + csvFilename;
        }

        private static ProductOption findOption(ISet<ProductOption> OptionList, int OptionTypeID)
        {
            foreach (ProductOption option in OptionList)
            {
                if (option.OptionType.ID == OptionTypeID)
                    return option;
            }
            return new ProductOption();
        }

        public static void doTestProcess()
        {
            if (HttpContext.Current == null)
                return;
            string ProcessID = (string)HttpContext.Current.Session["ProcessID"];
            InventoryImportProcess process = (InventoryImportProcess)HttpContext.Current.Cache[ProcessID];
            process.Status = ImportStatus.InProgress;
            process.Progress = 0;

            for (int i = 0; i < 20; i++)
            {
                process.Progress = i * 5;
                System.Threading.Thread.Sleep(2000);
            }
            process.Status = ImportStatus.Completed;
        }

        public static void UpdateProductInventory(InventoryImport ImportJob, string ProcessID)
        {
            ISession session = Configuration.GetCurrentSession();

            InventoryImportProcess process = (InventoryImportProcess)HttpContext.Current.Cache[ProcessID];
            process.Status = ImportStatus.InProgress;

            TanashWebsite website = session.Get<TanashWebsite>(ImportJob.WebsiteID);
            int WebsiteID = ImportJob.WebsiteID;

            FileStream fstream = new FileStream(HttpContext.Current.Server.MapPath("~/import/" + website.SubFolderName + "/" + ImportJob.CSVFileName), FileMode.Open);

            StreamReader reader = new StreamReader(fstream);

            ITransaction tx = session.BeginTransaction();
            List<int> existingProductIDList = new List<int>();

            string currencyChar = string.Format("{0:c}", 0).Substring(0, 1);

            int line = 0, column = 0;
            string columnName = "";
            try
            {
                // delete the original products
                SqlCommand cmd = (SqlCommand)Configuration.GetCurrentSession().Connection.CreateCommand();
                tx.Enlist(cmd);
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "update Product set Product.State = " + ((int)CMSObjectState.Deleted).ToString() + " where Product.WebsiteID = " + WebsiteID.ToString();
                cmd.ExecuteNonQuery();

                List<Product> products = new List<Product>();
                List<ProductOptionType> optionTypes = ProductOptionType.List(WebsiteID);
                Category defaultCategory = (Category)session.CreateQuery("select c from Category c where c.WebsiteID = " + WebsiteID.ToString() + " and c.IsDefault = true").UniqueResult();
                if (defaultCategory == null)
                {
                    List<Category> cats = (List<Category>)session.CreateQuery("select c from Category c where c.WebsiteID = " + WebsiteID.ToString() + " and c.ParentID = 0").List<Category>();
                    if (cats.Count > 0)
                        defaultCategory = cats[0];
                    else
                    {
                        defaultCategory = new Category();
                        defaultCategory.WebsiteID = website.ID;
                        defaultCategory.ParentID = 0;
                        defaultCategory.GUID = Guid.NewGuid().ToString();
                        defaultCategory.ObjectType = CMSObjectType.Category;
                        defaultCategory.State = CMSObjectState.Published;
                        defaultCategory.IsDefault = true;
                        session.Save(defaultCategory);
                    }
                }


                while (!reader.EndOfStream)
                {
                    line++;
                    string[] data = reader.ReadLine().Split(',');

                    if (line == 1) // ignore the header
                        continue;

                    Product product = null;
                    // check the UPC and see if we can find an existing product to update
                    if (data[1].Length > 0)
                    {
                        List<Product> testProds = (List<Product>)Configuration.GetCurrentSession().CreateQuery("select p from Product p where p.UPC = '" + data[1] + "' and p.WebsiteID = " + WebsiteID.ToString() + " and p.State = " + ((int)CMSObjectState.Published).ToString()).List<Product>();
                        if (testProds.Count > 0)
                        {
                            // found a matching product
                            product = testProds[0];
                            existingProductIDList.Add(testProds[0].ID);
                            testProds[0] = null;
                        }
                    }
                    if (product == null)
                    {
                        product = new Product();
                        product.GUID = Guid.NewGuid().ToString();
                    }
                    product.Title = data[0];
                    product.UPC = data[1];
                    column = 2;
                    columnName = "Price";
                    product.Price = double.Parse(data[2].Replace(currencyChar, ""));
                    column = 3;
                    columnName = "Discount";
                    product.Discount = double.Parse(data[3].Replace(currencyChar, ""));
                    string categoryName = data[4];
                    List<Category> cats = (List<Category>)session.CreateQuery("select c from Category c where c.WebsiteID = " + WebsiteID.ToString() + " and c.Title = '" + categoryName + "'").List<Category>();
                    if (cats.Count > 0)
                        product.CategoryID = cats[0].ID;

                    if (product.CategoryID == 0)
                        product.CategoryID = defaultCategory.ID;

                    int col = 5;
                    foreach (ProductOptionType optionType in optionTypes)
                    {
                        if (col >= data.Length)
                            break;


                        ProductOption option = null;
                        List<ProductOption> matchingOptions = (List<ProductOption>)session.CreateQuery("select po from ProductOption po where po.ProductID = " + product.ID.ToString() + " and po.OptionType.ID = " + optionType.ID.ToString()).List<ProductOption>();
                        if (matchingOptions.Count > 0)
                            option = matchingOptions[0];
                        else
                            option = new ProductOption();

                        option.OptionType = optionType;
                        option.Value = data[col];
                        option.State = CMSObjectState.Published;
                        option.ProductID = product.ID;
                        session.Save(option);
                        matchingOptions = null;
                        col++;
                    }

                    product.WebsiteID = website.ID;
                    product.State = CMSObjectState.Published;
                    products.Add(product);
                }

                foreach (Product product in products)
                {
                    session.Save(product);
                }


                string existingIDList = "";
                for (int i = 0; i < existingProductIDList.Count; i++)
                {
                    if (i > 0)
                        existingIDList += ",";

                    existingIDList += existingProductIDList[i].ToString();
                }


                session.Flush();
                tx.Commit();
                session.Disconnect();

                inventoryToXML(website);
            }
            catch (Exception ex)
            {
                tx.Rollback();
                session.Disconnect();
                string message = "Line " + line.ToString() + ": " + ex.Message;
                throw new Exception("Error updating inventory at line " + line.ToString() + ", column " + column.ToString() + " (" + columnName + ")", ex);
            }
        }

        public static void RefreshInventoryXML(int WebsiteID)
        {
            TanashWebsite website = Configuration.GetCurrentSession().Get<TanashWebsite>(WebsiteID);
            inventoryToXML(website);
        }
        public static void RefreshInventoryXML(TanashWebsite Website)
        {
            inventoryToXML(Website);
        }

        private static void inventoryToXML(TanashWebsite website)
        {


            List<Category> categories = Category.List(website.ID, 0);

            XmlWriterSettings settings = new XmlWriterSettings();
            settings.NewLineHandling = NewLineHandling.Entitize;
            settings.Indent = true;

            // Create a new XmlTextWriter instance
            // todo: is this inventory.xml file needed?
            FileStream fs = new FileStream(HttpContext.Current.Server.MapPath("~" + website.WebsiteFolder + "/inventory.xml"), FileMode.Create);

            XmlWriter w = XmlWriter.Create(fs, settings);

            w.WriteStartDocument();
            w.WriteStartElement("inventory");
            writeCategoryXml(w, website, categories);

            // products in no category
            List<Product> products = Product.List(website.ID, 0);
            foreach (Product product in products)
            {
                w.WriteStartElement("product");
                w.WriteAttributeString("id", product.ID.ToString());
                w.WriteAttributeString("title", product.Title);
                w.WriteAttributeString("UPC", product.UPC);
                w.WriteAttributeString("Price", product.Price.ToString());
                w.WriteAttributeString("Discount", product.Discount.ToString());
                List<ProductOption> options = ProductOption.List(product.ID);
                foreach (ProductOption option in options)
                {
                    w.WriteStartElement("option");
                    w.WriteAttributeString("id", option.ID.ToString());
                    w.WriteAttributeString("typeid", option.OptionType.ID.ToString());
                    w.WriteAttributeString("title", option.OptionType.Title);
                    w.WriteValue(option.Value);
                    w.WriteEndElement();
                }
                w.WriteEndElement();
            }

            w.WriteEndElement();
            w.WriteEndDocument();
            w.Close();
            fs.Close();
            fs.Dispose();

        }

        private static void writeCategoryXml(XmlWriter w, TanashWebsite website, List<Category> categories)
        {
            foreach (Category cat in categories)
            {
                w.WriteStartElement("category");
                w.WriteAttributeString("id", cat.ID.ToString());
                w.WriteAttributeString("title", cat.Title);

                List<Category> children = Category.List(cat.WebsiteID, cat.ID);
                if (children.Count > 0)
                    writeCategoryXml(w, website, children);

                List<Product> products = Product.List(website.ID, cat.ID);
                foreach (Product product in products)
                {
                    w.WriteStartElement("product");
                    w.WriteAttributeString("id", product.ID.ToString());
                    w.WriteAttributeString("title", product.Title);
                    w.WriteAttributeString("UPC", product.UPC);
                    w.WriteAttributeString("Price", product.Price.ToString());
                    w.WriteAttributeString("Discount", product.Discount.ToString());
                    List<ProductOption> options = ProductOption.List(product.ID);
                    foreach (ProductOption option in options)
                    {
                        w.WriteStartElement("option");
                        w.WriteAttributeString("id", option.ID.ToString());
                        w.WriteAttributeString("typeid", option.OptionType.ID.ToString());
                        w.WriteAttributeString("title", option.OptionType.Title);
                        w.WriteValue(option.Value);
                        w.WriteEndElement();
                    }
                    w.WriteEndElement();
                }
                w.WriteEndElement();
            }
        }
    }

    public class ImportInventoryTask : WorkerTask
    {
        InventoryImportProcess mImportProcess;
        public InventoryImportProcess ImportProcess { get { return mImportProcess; } set { mImportProcess = value; } }
        public string dataFilePath = "";

        public ImportInventoryTask(string TaskID)
            : base(TaskID)
        {
            dataFilePath = HttpContext.Current.Server.MapPath(HttpContext.Current.Request.ApplicationPath.TrimEnd('/') + "/data").TrimEnd('\\');
            if (!Directory.Exists(dataFilePath))
                Directory.CreateDirectory(dataFilePath);
        }

        public override void Run()
        {
            mImportProcess.Status = ImportStatus.InProgress;
            mImportProcess.Progress = 0;

            for (int i = 0; i < 20; i++)
            {
                mImportProcess.Progress = i * 5;
                TaskHelpers.UpdateStatus(dataFilePath, _taskID, mImportProcess);
                System.Threading.Thread.Sleep(2000);
            }
            mImportProcess.Status = ImportStatus.Completed;
            _results = mImportProcess;
            TaskHelpers.clearStatus(dataFilePath, _taskID);
        }

        public override void Run(params object[] args)
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }

    public abstract class WorkerTask
    {
        protected object _results;
        protected string _taskID;

        public WorkerTask(string taskID)
        {
            _taskID = taskID;
        }

        public abstract void Run();
        public abstract void Run(params object[] args);

        public object Results
        {
            get { return _results; }
        }
    }

    public class TaskHelpers
    {
        public static void UpdateStatus(string path, string taskID, object info)
        {
            FileStream fs = new FileStream(path + "\\" + taskID, FileMode.Create);

            System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(InventoryImportProcess));
            ser.Serialize(fs, (InventoryImportProcess)info);

            fs.Close();
            fs.Dispose();
        }

        public static InventoryImportProcess getStatus(string taskID)
        {
            if (HttpContext.Current == null)
                return new InventoryImportProcess();

            string path = HttpContext.Current.Server.MapPath(HttpContext.Current.Request.ApplicationPath.TrimEnd('/') + "/data");

            FileStream fs = new FileStream(path.TrimEnd('\\') + "\\" + taskID, FileMode.Open);
            if (fs.CanSeek)
                fs.Seek(0, SeekOrigin.Begin);

            System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(InventoryImportProcess));

            fs.Close();

            InventoryImportProcess process = (InventoryImportProcess)ser.Deserialize(fs);

            return process;
        }

        public static void clearStatus(string path, string taskID)
        {
            HttpContext context = HttpContext.Current;
            File.Delete(path + "\\" + taskID);
        }
    }

}
